package provide proxy 0.3

namespace eval proxy {

set options {-server -port -user -password -type}
# Return list of available proxy types
proc types {} { 
	set lst [list]
	foreach x [namespace children] {lappend lst [namespace tail $x]}
	set lst
}

# Enumerate all configured proxies
proc enum {} {
	set lst [list]
	foreach x [info vars [namespace current]::proxy-*] {
		lappend lst [string range [namespace tail $x] 6 end]
	}
	set lst
}

# Create or configure existent proxy
proc proxy {name args} {
	upvar #0 [namespace current]::proxy-$name data
	variable options
	foreach {key val} $args {
		if {[lsearch $options $key]==-1} {
			return -code error "Unknown option $key"
		}
		if {[llength [info commands check$key]]} { check$key $val }	
		set data($key) $val
	}
	# if proxy type is not specified, pick one as default
	if {![info exists data(-type)]} { set data(-type) [lindex [types] 0] }
	if {$data(-type)==""} { return -code error "No proxy types defined" }
	if {[llength [info commands $data(-type)::adjust]]} {
		$data(-type)::adjust [namespace current]::proxy-$name
	}
	set name
}

# Delete proxy by name
proc delete {name} {
	set id [namespace current]::proxy-$name
	if {[info exists $id]} {unset $id}
	set name
}

# Perform actual connection to server via named proxy
proc connect {name server port script_ok script_error {logger empty}} {
	set id [namespace current]::proxy-$name
	upvar #0 $id data
	if {![info exists data]} {
		return -code error "Proxy $name does not exists"
	}
	if {![info exists data(-server)] || ![info exists data(-port)]} {
		return -code error "Proxy server or port is not goven"
	}
	$data(-type)::connect $id $server $port $script_ok $script_error $logger
}

proc check-type {type} {
	if {[lsearch [types] $type]==-1} {
		return -code error "Wrong proxy type $type. Valid types are [join [types] {, }]"
	}
}

proc check-port {port} {
	if {![string is integer $port] || $port<0 || $port>65535} {
		return -code error "Port should be in range 0-65535"
	}
}

# Http proxy
namespace eval http {

# Adjust parameters for http proxy
proc adjust {id} {
	upvar #0 $id data
	if {[info exists data(-user)] && $data(-user)!=""} {
		package require base64 
		set pass ""
		if {[info exists data(-password)]} { set pass $data(-password) }
		set auth [::base64::encode $data(-user):$pass]
		set data(auth) "Proxy-Authorization: Basic $auth"
	} elseif {[info exists data(auth)]} { unset data(auth)}
	if {![info exists data(-port)]} { set data(-port) 8080 }
}

proc connect {id server port script_ok script_error log} {
	upvar #0 $id data 
	eval $log info "{Connecting to proxy $data(-server):$data(-port)}"
	if {[catch {
		set sock [socket -async $data(-server) $data(-port)]
	    } v]} { 
	    eval $script_error 0 [list $v]
	    return
	} 
	fconfigure $sock -translation crlf -buffering line -blocking no
	fileevent $sock writable [list [namespace current]::write $id\
		$sock $server $port $script_ok $script_error $log]
}

proc write {id sock server port script_ok script_error log} {
	upvar #0 $id data 
	set msg "CONNECT $server HTTP/1.0"
	if {[info exists data(auth)]} { append msg "\n" $data(auth) }
	if {[catch { puts $sock "$msg\n"; flush $sock } v]} {
		eval $script_error 0 [list $v]
	} else {	
		fileevent $sock writable {}
		fileevent $sock readable [list [namespace current]::read\
			$id $sock $script_ok $script_error $log]
	}		
}

proc read {id sock script_ok script_error log} {
	upvar #0 $id data
	upvar #0 [namespace current]::conn-$sock conn
	set count [gets $sock http_resp]
	if {$count==-1} {
		fileevent $sock readable {}
		eval $script_error [list 0 "Connection closed"]
	} elseif {$count==0} {
		fileevent $sock readable {}
		if {$conn(code)==200} {
			eval $script_ok $sock
		} elseif {$conn(code)==407} {
			eval $script_error [list auth $conn(descr)]
		} else {
			eval $script_error [list $conn(code) $conn(descr)]
		}
		unset conn
	} else {
		regexp {^HTTP[^\s]+\s+(\d+)\s+(.*)$} $http_resp _ \
			conn(code) conn(descr)
	}
}

	
}
}
